﻿using Avin.Infrastructure.DbContext;
using Avin.Infrastructure.Entity;
using Avin.Infrastructure.Linq.ToDbScript;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Avin.Infrastructure.Linq
{
    public abstract class QueryProvider : IQueryProvider
    {
        private IDbContextBase _dbContext = null;

        private readonly QueryInfo _queryInfo = new();

        IQueryable<T> IQueryProvider.CreateQuery<T>(Expression expression)
        {
            Console.WriteLine("IQueryProvider.CreateQuery<T>");

            if (expression is MethodCallExpression mcExp)
                ResolveMethodExpression(mcExp);

            return new Queryable<T>(this, expression);
        }

        IQueryable IQueryProvider.CreateQuery(Expression expression)
        {
            Console.WriteLine("IQueryProvider.CreateQuery");

            Type elementType = TypeSystem.GetElementType(expression.Type);
            try
            {
                return (IQueryable)Activator.CreateInstance(typeof(Queryable<>).MakeGenericType(elementType), new object[] { this, expression });
            }
            catch (TargetInvocationException tie)
            {
                throw tie.InnerException;
            }
        }

        T IQueryProvider.Execute<T>(Expression expression)
        {
            Console.WriteLine("IQueryProvider.Execute<T>");

            if (expression is MethodCallExpression mcExp)
            {
                ResolveMethodExpression(mcExp);

                AddDefaultSelectInfo(mcExp.Arguments[0]);

                var dbSql = ToDbScriptManager.Get(_dbContext.DbProviderFactory);
                string strSql;
                if (mcExp.Method.Name == "First" || mcExp.Method.Name == "FirstOrDefault")
                {
                    strSql = dbSql.SelectTop(_queryInfo, 1);
                    PrintParam(strSql, _queryInfo);
                    var data = ToData.FirstOrDefault<T>(_dbContext, strSql, ToDbParameters(_queryInfo.ParamInfos));
                    if (data == null && mcExp.Method.Name == "First")
                        throw new InvalidOperationException("data is null");

                    return data;
                }

                if (mcExp.Method.Name == "Single" || mcExp.Method.Name == "SingleOrDefault")
                {
                    strSql = dbSql.Count(_queryInfo);
                    PrintParam(strSql, _queryInfo);
                    int count = ToData.Count(_dbContext, strSql, ToDbParameters(_queryInfo.ParamInfos));
                    if (count == 0)
                    {
                        if (mcExp.Method.Name == "Single")
                            throw new InvalidOperationException("data is null");
                        return default;
                    }

                    if (count > 1)
                        throw new Exception("record is not noly one");

                    strSql = dbSql.Select(_queryInfo);
                    PrintParam(strSql, _queryInfo);
                    return ToData.SingleOrDefault<T>(_dbContext, strSql, ToDbParameters(_queryInfo.ParamInfos));
                }

                if (mcExp.Method.Name == "Count")
                {
                    strSql = dbSql.Count(_queryInfo);
                    PrintParam(strSql, _queryInfo);
                    object count = ToData.Count(_dbContext, strSql, ToDbParameters(_queryInfo.ParamInfos));
                    return (T)count;
                }
            }
            return default;
        }

        object IQueryProvider.Execute(Expression expression)
        {
            //Console.WriteLine("IQueryProvider.Execute");

            //AddDefaultTableInfo(expression);
            //AddDefaultSelectInfo(expression);

            //var dbSql = ToDbScriptManager.Get(_dbContext.DbProviderFactory);
            //string strSql;
            //if (_queryInfo.TakeInfo.NeedDo)
            //    strSql = dbSql.SelectTake(_queryInfo);
            //else
            //    strSql = dbSql.Select(_queryInfo);
            return default;
        }

        public object Execute<T>(Expression expression)
        {
            Console.WriteLine("Execute<T>");

            AddDefaultTableInfo(expression);
            AddDefaultSelectInfo(expression);

            var dbSql = ToDbScriptManager.Get(_dbContext.DbProviderFactory);
            string strSql;
            if (_queryInfo.TakeInfo.NeedDo)
                strSql = dbSql.SelectTake(_queryInfo);
            else
                strSql = dbSql.Select(_queryInfo);

            PrintParam(strSql, _queryInfo);
            return ToData.ToList<T>(_dbContext, strSql, ToDbParameters(_queryInfo.ParamInfos));
        }

        private static void PrintParam(string sql, QueryInfo queryInfo)
        {
            Console.WriteLine(sql);
            if (queryInfo.ParamInfos.Count > 0)
            {
                foreach (var p in queryInfo.ParamInfos)
                {
                    Console.WriteLine($"name：{p.Name}；value：{p.Value}");
                }
            }
        }

        /// <summary>
        /// 设置数据上下文
        /// </summary>
        /// <param name="dbContext"></param>
        protected void SetDbContext(IDbContextBase dbContext)
        {
            _dbContext = dbContext;
        }

        /// <summary>
        /// 重置所有查询信息
        /// </summary>
        public void ResetQuery()
        {
            _queryInfo.Reset();
        }

        /// <summary>
        /// 解析Linq方法
        /// </summary>
        /// <param name="expression"></param>
        private void ResolveMethodExpression(MethodCallExpression expression)
        {
            AddDefaultTableInfo(expression.Arguments[0]);

            string strMethodName = expression.Method.Name;
            if (strMethodName == "Select")
            {
                ResolveSelectExpression(((expression.Arguments[1] as UnaryExpression).Operand as LambdaExpression).Body);
            }
            else if (strMethodName == "Join")
            {
                ResolveJoinExpression(expression);
            }
            else if (strMethodName == "Where"
                    || strMethodName == "Count"
                    || strMethodName == "First"
                    || strMethodName == "FirstOrDefault"
                    || strMethodName == "Single"
                    || strMethodName == "SingleOrDefault")
            {
                if (expression.Arguments.Count > 1)
                    ResolveWhereExpression(expression.Arguments[1]);
            }
            else if (strMethodName == "OrderBy")
            {
                ResolveOrderByExpression(expression.Arguments[1]);
            }
            else if (strMethodName == "OrderByDescending")
            {
                ResolveOrderByExpression(expression.Arguments[1], "desc");
            }
            else if (strMethodName == "GroupBy")
            {
                if (expression.Arguments.Count > 2)
                    ResolveGroupByExpression(expression.Arguments[2], true);

                ResolveGroupByExpression(expression.Arguments[1], false);
            }
            else if (strMethodName == "Skip"
                    || strMethodName == "Take")
            {
                ResolveSkipTakeExpression(expression);
            }
            else
            {
                Console.WriteLine(strMethodName);
            }
        }

        /// <summary>
        /// 添加默认表信息
        /// </summary>
        private void AddDefaultTableInfo(Expression expression)
        {
            if (_queryInfo.TableInfos.Count == 0)
                if (expression is ConstantExpression cExp)
                {
                    if (ExpressionResolver.IsAssignableFromIQueryable(cExp.Value.GetType()))
                    {
                        var queryable = cExp.Value as IQueryable<object>;
                        if (!_queryInfo.TableInfos.Exists(a => a.HashCode == queryable.ElementType.GetHashCode()))
                        {
                            _queryInfo.TableInfos.Add(new TableInfo
                            {
                                HashCode = queryable.ElementType.GetHashCode(),
                                JoinType = _queryInfo.TableInfos.Count > 0 ? "" : "from",
                                SourceName = EntityInitializer.GetTableName(_dbContext.DbProviderFactory, queryable.ElementType),
                                OtherName = "T" + _queryInfo.TableInfos.Count
                            });
                        }
                    }
                }
        }

        /// <summary>
        /// 添加默认查询信息
        /// </summary>
        private void AddDefaultSelectInfo(Expression expression)
        {
            if (_queryInfo.SelectInfos.Count == 0)
                if (expression is ConstantExpression cExp)
                {
                    if (ExpressionResolver.IsAssignableFromIQueryable(cExp.Value.GetType()))
                    {
                        var queryable = cExp.Value as IQueryable<object>;
                        var tableInfo = _queryInfo.TableInfos.SingleOrDefault(a => a.HashCode == queryable.ElementType.GetHashCode());
                        if (tableInfo != null)
                        {
                            _queryInfo.SelectInfos.Add(new SelectInfo
                            {
                                FieldName = tableInfo.OtherName + ".*",
                                OtherName = ""
                            });
                        }
                    }
                }
        }

        /// <summary>
        /// 解析select
        /// </summary>
        /// <param name="expression"></param>
        private void ResolveSelectExpression(Expression expression)
        {
            if (expression is NewExpression nExp)
            {
                //清除查询信息
                _queryInfo.SelectInfos.Clear();

                int i = 0;
                foreach (var item in nExp.Arguments)
                {
                    _queryInfo.SelectInfos.Add(new SelectInfo
                    {
                        FieldName = ExpressionResolver.ResolveOperatorExpression(item, _dbContext.DbProviderFactory, _queryInfo),
                        OtherName = nExp.Members[i].Name
                    });
                    i++;
                }
            }
            else if (expression is MemberInitExpression miExp)
            {
                //清除查询信息
                _queryInfo.SelectInfos.Clear();
                foreach (MemberAssignment item in miExp.Bindings)
                {
                    _queryInfo.SelectInfos.Add(new SelectInfo
                    {
                        FieldName = ExpressionResolver.ResolveOperatorExpression(item.Expression, _dbContext.DbProviderFactory, _queryInfo),
                        OtherName = item.Member.Name
                    });
                }
            }
            else
            {
                Console.WriteLine(expression.GetType());
            }
        }

        /// <summary>
        /// 解析Join
        /// </summary>
        /// <param name="expression"></param>
        private void ResolveJoinExpression(Expression expression, string joinType = "left join")
        {
            var mcExp = expression as MethodCallExpression;
            var queryable = (mcExp.Arguments[1] as ConstantExpression).Value as IQueryable<object>;
            if (_queryInfo.TableInfos.Exists(a => a.HashCode == queryable.ElementType.GetHashCode()))
                return;

            var tableInfo = new TableInfo
            {
                HashCode = queryable.ElementType.GetHashCode(),
                JoinType = joinType,
                SourceName = EntityInitializer.GetTableName(_dbContext.DbProviderFactory, queryable.ElementType),
                OtherName = "T" + _queryInfo.TableInfos.Count
            };
            _queryInfo.TableInfos.Add(tableInfo);
            string strLeft = ExpressionResolver.ResolveConditionExpression(((mcExp.Arguments[2] as UnaryExpression).Operand as LambdaExpression).Body, _dbContext.DbProviderFactory, _queryInfo);
            string strRight = ExpressionResolver.ResolveConditionExpression(((mcExp.Arguments[3] as UnaryExpression).Operand as LambdaExpression).Body, _dbContext.DbProviderFactory, _queryInfo);
            _queryInfo.TableInfos[^1].Where.Append("on " + strLeft + " = " + strRight);

            if (mcExp.Arguments[4] is UnaryExpression uExp)
            {
                if (uExp.Operand is LambdaExpression u_lExp)
                {
                    ResolveSelectExpression(u_lExp.Body);
                }
            }
        }

        /// <summary>
        /// 解析Where
        /// </summary>
        /// <param name="expression"></param>
        private void ResolveWhereExpression(Expression expression)
        {
            var strResult = ExpressionResolver.ResolveConditionExpression(expression, _dbContext.DbProviderFactory, _queryInfo);
            if (_queryInfo.Where.Length > 0)
                _queryInfo.Where.Append(" and " + strResult);
            else
                _queryInfo.Where.Append(strResult);
        }

        /// <summary>
        /// 解析GroupBy
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="isSelect">是否select部分</param>
        private void ResolveGroupByExpression(Expression expression, bool isSelect = false)
        {
            var uExp = expression as UnaryExpression;
            var u_lExp = uExp.Operand as LambdaExpression;
            if (u_lExp.Body is MemberExpression u_l_mExp)//a.Id
            {
                var groupByInfo = new GroupByInfo
                {
                    FieldName = ExpressionResolver.ResolveOperatorExpression(u_l_mExp, _dbContext.DbProviderFactory, _queryInfo),
                    OtherName = ""
                };
                if (isSelect)
                    _queryInfo.GroupNewInfos.Add(groupByInfo);
                else
                    _queryInfo.GroupByInfos.Add(groupByInfo);
            }
            else if (u_lExp.Body is NewExpression m_nExp)//new{Title = a.Title}
            {
                int i = 0;
                foreach (var item in m_nExp.Arguments)
                {
                    if (item is MemberExpression item_mExp)//new{Title = a.Title}
                    {
                        var groupByInfo = new GroupByInfo
                        {
                            FieldName = ExpressionResolver.ResolveOperatorExpression(item_mExp, _dbContext.DbProviderFactory, _queryInfo),
                            OtherName = m_nExp.Members[i].Name
                        };
                        if (isSelect)
                            _queryInfo.GroupNewInfos.Add(groupByInfo);
                        else
                            _queryInfo.GroupByInfos.Add(groupByInfo);
                    }
                    else if (item is BinaryExpression item_bExp)//new{资产 = a.现金+a.资产}
                    {
                        var groupByInfo = new GroupByInfo
                        {
                            FieldName = ExpressionResolver.ResolveOperatorExpression(item_bExp, _dbContext.DbProviderFactory, _queryInfo),
                            OtherName = m_nExp.Members[i].Name
                        };
                        if (isSelect)
                            _queryInfo.GroupNewInfos.Add(groupByInfo);
                        else
                            _queryInfo.GroupByInfos.Add(groupByInfo);
                    }
                    i++;
                }
            }
        }

        /// <summary>
        /// 解析OrderBy
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="orderby"></param>
        private void ResolveOrderByExpression(Expression expression, string orderby = "asc")
        {
            string strFieldName = ExpressionResolver.ResolveOperatorExpression(expression, _dbContext.DbProviderFactory, _queryInfo);
            _queryInfo.OrderByInfos.Add(new OrderByInfo
            {
                FieldName = strFieldName,
                OrderBy = orderby
            });
        }

        /// <summary>
        /// 解析SkipTake
        /// </summary>
        /// <param name="expression"></param>
        private void ResolveSkipTakeExpression(Expression expression)
        {
            var mcExp = expression as MethodCallExpression;
            int value = -1;

            if (mcExp.Arguments[1] is ConstantExpression mc_cExp)
                value = (int)mc_cExp.Value;
            else if (mcExp.Arguments[1] is MethodCallExpression mc_mcExp)
                value = (int)Expression.Lambda(mc_mcExp).Compile().DynamicInvoke();
            else if (mcExp.Arguments[1] is MemberExpression mc_mExp)
                value = (int)Expression.Lambda(mc_mExp).Compile().DynamicInvoke();

            if (value < 0)
                return;

            var strMethodName = mcExp.Method.Name;
            _queryInfo.TakeInfo.NeedDo = true;
            if (strMethodName == "Skip")
                _queryInfo.TakeInfo.Skip = (uint)value;
            else
                _queryInfo.TakeInfo.Take = (uint)value;
        }

        /// <summary>
        /// 转换成DbParameter
        /// </summary>
        /// <param name="paramInfos"></param>
        /// <returns></returns>
        private DbParameter[] ToDbParameters(List<ParamInfo> paramInfos)
        {
            if (paramInfos == null || paramInfos.Count == 0)
                return null;

            List<DbParameter> parameters = new();
            foreach (var p in paramInfos)
            {
                var param = _dbContext.DbProviderFactory.CreateParameter();
                if (param == null)
                    continue;
                param.ParameterName = p.Name;
                param.Value = p.Value ?? DBNull.Value;
                parameters.Add(param);
            }
            return parameters.ToArray();
        }
    }
}
